//#define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include "tracelog.h"
#include "dllmain.h"
#include "w_main.h"
#include "trayicon.h"
#include "w_detail.h"
#include "log.h"
#include "db_log.h"
#include "ini.h"
#include "listview.h"
//#include <stdlib.h>
//#include <stdio.h>
//#include "resource.h"

HWND	hwndMain = NULL;
BOOL	is_tracelog_init = false;

HANDLE	hThread  = NULL;
FILE	*fOutLog = NULL;  //handle to output text log file
FILE	*fTmpW = NULL;  //handle to write tmp buffer
FILE	*fTmpR = NULL;  //handle to read tmp buffer
_TCHAR	*cTmpName=NULL;
_TCHAR	maskInfo[32][32];
extern  long delayLog;

//common settings
unsigned long maskSet;
unsigned long basicMaskSet; //the maskSet passed from calling process (tracelog_start)
BOOL          showTraceLog=false;


unsigned long getMaskSet(){
	return maskSet;
}	
unsigned long getBasicMaskSet(){
	return basicMaskSet;
}
BOOL getShowTraceLog(){
	return showTraceLog;
}

unsigned long setMaskSet(unsigned long i){
	if(maskSet!=i){
		maskSet=i;
		ini_setint(_T("options"),_T("mask.debug"),maskSet);
		clearWindowLog();
		tracelog_rewind();
		PostMessage(hwndMain,MYWM_REFRESHDATA,0,0);
	}
	return maskSet;
}

_TCHAR * getMaskInfo(unsigned int mask) {
	int i=0;
	while(mask>1){
		i++;
		mask=mask>>1;
	}
	if(i>=32)i=31;
	return maskInfo[i];
}

//opens tmp file for read and write
BOOL tmp_init(){
	_TCHAR * fpath=NULL;
	long fnlen=	GetTempPath(0,NULL)+5;
	fpath=new _TCHAR[fnlen];
	GetTempPath(fnlen,fpath);
	fnlen+=MAX_PATH;
	cTmpName=new _TCHAR[fnlen];
	GetTempFileName(fpath, _T("opa"), 0, cTmpName);
	delete []fpath;

	log(_T("DBG: tmp_init: tmp file = \"%s\".\n"),cTmpName);
	fTmpW=_tfopen(cTmpName,_T("wb"));
	fTmpR=_tfopen(cTmpName,_T("rb"));
	if (fTmpW && fTmpR) return true;
	log(_T("ERR: tmp_init:error opening tmp file \"%s\": W=%X R=%X.\n"),cTmpName,fTmpW,fTmpR);
	return false;
}

//closes tmp files
BOOL tmp_finit(){
	if (fTmpW) {
		log(_T("DBG: tmp_finit: close fTmpW.\n"));
		fclose(fTmpW);
		fTmpW=NULL;
	}
	if (fTmpR) {
		log(_T("DBG: tmp_finit: close fTmpR.\n"));
		fclose(fTmpR);
		fTmpR=NULL;
	}
	if (cTmpName) {
		log(_T("DBG: tmp_finit: delete tmp file \"%s\".\n"),cTmpName);
		_tremove(cTmpName);
		delete []cTmpName;
		cTmpName=NULL;
	}
	return true;
}


DWORD WINAPI threadProc(void* pData) {
	MSG msg;
	hwndMain=createMainWindow(getInstance(),(_TCHAR*)pData);
	if (!hwndMain) {
		hwndMain=(HWND)-1; //to let calling thread catch error
		return 0;
	}
	if (!createDetailWindow(hwndMain)){
		DestroyWindow(hwndMain);
		hwndMain=(HWND)-1; //to let calling thread catch error
		return 0;
	}
	log(_T("DBG: threadProc:start window loop.\n"));
	while (GetMessage (&msg, NULL, 0, 0)) {
		if( IsDialogMessage( hwndMain, &msg ) ) continue;
		TranslateMessage (&msg);
		DispatchMessage (&msg);
	} 
	log(_T("DBG: threadProc:end window loop.\n"));
	DestroyWindow(hwndMain);
	hwndMain=NULL;
	return 0;
}


//creates new thread, log window
//returns TRUE if everything is Ok.
BOOL tracelog_init(){
	DWORD dwThreadID;
	//_TCHAR  buf[10];
	basicMaskSet=ini_getBasicMaskSet();
	maskSet=ini_getint(_T("options"),_T("mask.debug"),basicMaskSet);
	
	showTraceLog=ini_getShowTraceLog();
	
	if(basicMaskSet){
		_TCHAR*LOGENVPATH=_T("%HOMEDRIVE%%HOMEPATH%");
		log(_T("DBG: tracelog_init: basicMaskSet=%i...\n"),basicMaskSet);
		//need to create output log
		_TCHAR*logpath;
		SYSTEMTIME st;
		log(_T("DBG: tracelog_init: LOGPENVPATH=%s\n"),LOGENVPATH);
		long logpath_len=ExpandEnvironmentStrings(LOGENVPATH,NULL,0)+100;
		logpath=new _TCHAR[logpath_len];
		logpath_len=ExpandEnvironmentStrings(LOGENVPATH,logpath,logpath_len);
		logpath_len=_tcslen(logpath);
		log(_T("DBG: tracelog_init: logpath=%s\n"),logpath);
		if(logpath[logpath_len-1]==_T('\\') || logpath[logpath_len-1]==_T('/') )logpath_len--;
		_tcscpy(logpath+logpath_len,_T("\\tracelog"));
		log(_T("DBG: tracelog_init: logpath=%s\n"),logpath);
		CreateDirectory(logpath,NULL);
		GetLocalTime(&st);
		_stprintf(logpath+_tcslen(logpath),_T("\\%04i%02i%02i%02i%02i%02i.log"),st.wYear,st.wMonth,st.wDay,st.wHour, st.wMinute, st.wSecond);
		log(_T("DBG: tracelog_init: logpath=%s\n"),logpath);
		fOutLog=_tfopen(logpath,_T("wt"));
		delete []logpath;
	}
	//init
	if(showTraceLog){
		log(_T("DBG: tracelog_init: showTraceLog=%i...\n"),showTraceLog);
		//init maskInfo stuct
		for(int i=0;i<32;i++){
			_TCHAR key[20];
			_stprintf(key,_T("i %u"),(1<<i));
			ini_getstr(_T("tracelog.maskinfo"),key+2,key,maskInfo[i],32);
		}
		if (!tmp_init()) return false;
		log(_T("DBG: tracelog_init:Creating Thread...\n"));
		hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)threadProc, NULL, 0, &dwThreadID);
		if (hThread == NULL) {
			log_error(_T("tracelog_init:CreateThread"));
			return false;
		}
		log(_T("DBG: tracelog_init:CreateThread OK.\n"));
		//wait for window creation
		while(!hwndMain)Sleep(111);
		if(hwndMain==(HWND)-1)hwndMain=NULL;
		if(!hwndMain)return false;
		log(_T("DBG: tracelog_init:Main window created.\n"));
	}
	log(_T("DBG: tracelog_init: OK.\n"));
	return true;
}
	
	
//destroy thread, log window, free ressources
extern "C" int  __stdcall tracelog_stop(){
	log(_T("DBG: tracelog_stop: enter.\n"));
	//return_message_callback();
	if (hwndMain) SendMessage(hwndMain,WM_CLOSE,0,0);
	if (hThread) {
		DWORD dw = WaitForSingleObject(hThread, 3000);
		switch (dw) { 
			case WAIT_OBJECT_0: 
				log(_T("DBG: tracelog_stop:Thread exited OK.\n"));
				break; 
			case WAIT_TIMEOUT: 
				log(_T("ERR: tracelog_stop:Thread exited WAIT_TIMEOUT.\n"));
				TerminateThread(hThread, 0);
				break; 
			case WAIT_FAILED:
				log(_T("ERR: tracelog_stop:Thread exited WAIT_FAILED.\n"));
				TerminateThread(hThread, 0);
				break; 
		}
		CloseHandle(hThread);
		hThread = NULL;
	}
	tmp_finit();
	if(fOutLog)fclose(fOutLog);
	return 1;
}


//specially for the PROCESS_DETACH
//forse to free resources
int tracelog_terminate(){
	//return_message_callback();
	if (hwndMain) {
		log(_T("DBG: tracelog_terminate: window close\n"));
		SendMessage(hwndMain,WM_CLOSE,0,0);
		log(_T("DBG: tracelog_terminate: destroy window\n"));
		DestroyWindow(hwndMain);
		hwndMain=NULL;
	}
	if (hThread) {
		log(_T("DBG: tracelog_terminate: thread terminate...\n"));
		TerminateThread(hThread, 0);
		CloseHandle(hThread);
		log(_T("DBG: tracelog_terminate: thread terminated\n"));
		hThread = NULL;
	}
	tmp_finit();
	if(fOutLog)fclose(fOutLog);
	return 1;
}

BOOL write_outlog(int mask, SYSTEMTIME*st, _TCHAR*obj, _TCHAR*msg){
	log(_T("DBG: write_outlog: enter.\n"));
	for(int i=0; msg[i]; i++)if(msg[i]==_T('\r') || msg[i]==_T('\n'))msg[i]=_T(' ');
	_ftprintf(fOutLog,_T("%7u\t%02i/%02i/%04i %02i:%02i:%02i.%03i\t%s\t%s\n"),mask,
		st->wDay,st->wMonth,st->wYear,st->wHour, st->wMinute, st->wSecond, st->wMilliseconds,
		obj, msg);
	fflush(fOutLog); //added on request of XABA to have online log update
	log(_T("DBG: write_outlog: exit.\n"));
	return true;
}

//add a new message to the log
extern "C" BOOL __stdcall tracelog_add( int mask, _TCHAR * obj, _TCHAR * msg){
	log(_T("DBG: tracelog_add: enter.\n"));
	_TCHAR	*nullObject=_T(" ");
	_TCHAR	*nullMessage=_T("<NULL> message");
	_TCHAR	*emptyMessage=_T("<EMPTY> message");
	_TCHAR	*badPointer=_T("bad string pointer: 0x                 ");

	//log(_T("DBG: call tracelog_add: obj:%s, msg:%s.\n"), obj, msg);

	if( !is_tracelog_init ){
		log(_T("DBG: tracelog_add: tracelog was not initiated, so do it.\n"));
		tracelog_init();
		is_tracelog_init=true;
	}
	if(obj==NULL)obj=nullObject;
	if(msg==NULL)msg=nullMessage;
	
	if(IsBadStringPtr(obj,1)){
		_stprintf(badPointer+22,_T("%X"),obj);
		obj=badPointer;
	}
	if(IsBadStringPtr(msg,1)){
		_stprintf(badPointer+22,_T("%X"),msg);
		msg=badPointer;
	}
	if(obj[0]==0)obj=nullObject;
	if(msg[0]==0)msg=emptyMessage;

	SYSTEMTIME st;
	GetLocalTime(&st);

	if(showTraceLog){
		int  obj_len =(_tcslen(obj) + 1);// * sizeof(_TCHAR);//!!!!!!!!!!!!!!!!!
		int  msg_len =(_tcslen(msg) + 1);// * sizeof(_TCHAR);
		//every int value we'll store in static data length = 10
		int  len= sizeof(int) /*for this value*/
				+ sizeof(int) /*for mask*/ 
				+ sizeof(SYSTEMTIME) /*for current time*/
				+ sizeof(int) /*for obj_len*/ + obj_len*sizeof(_TCHAR) 
				+ sizeof(int) /*for msg_len*/ + msg_len*sizeof(_TCHAR);
		log(_T("DBG: tracelog_add len:%i, obj_len:%i, msg_len:%i\n"), len, obj_len, msg_len);

		//one row of data will contain + len (int) in the head 
		//			- the total length of row including the length of "len" itself

		//write data to the temp buffer
		fwrite(    &len, sizeof(int),        1, fTmpW);
		fwrite(   &mask, sizeof(int),        1, fTmpW);
		fwrite(     &st, sizeof(SYSTEMTIME), 1, fTmpW);
		fwrite(&obj_len, sizeof(int),        1, fTmpW);
		fwrite(     obj, obj_len*sizeof(_TCHAR),        1, fTmpW);
		fwrite(&msg_len, sizeof(int),        1, fTmpW);
		fwrite(     msg, msg_len*sizeof(_TCHAR),        1, fTmpW);
		//
		fflush(fTmpW);
		//notify the window to refresh view from buffer
		PostMessage(hwndMain,MYWM_REFRESHDATA,0,0);
		if(delayLog && maskSet&mask)Sleep(delayLog);
	}

	if(basicMaskSet&mask)write_outlog(mask,&st,obj,msg);
	log(_T("DBG: tracelog_add: exit.\n"));
	return true;
}


//reads next 1 row from tmp buffer, adds it to the list view if mask is ok
//returns:
//    -1  if EOF
//     0  if row was read but was not accepted
//     1  if row was read and was accepted
int tracelog_read(HWND hwndLV){

	log(_T("DBG: tracelog_read: enter.\n"));
	//should be smart function to avoid reading uncommited rows
	long pos=ftell(fTmpR); //remember current position to rollback in case of error reading
	int len = 0, obj_len = 0, msg_len = 0, bmask = 0;
	_TCHAR *msg=NULL;
	_TCHAR *obj=NULL;
	SYSTEMTIME st;
	if (fread( &len, sizeof(int), 1, fTmpR)!=1) goto err;
	if (fread( &bmask, sizeof(int), 1, fTmpR)!=1) goto err;
	log(_T("DBG: tracelog_read: 1.\n"));
	if (maskSet&bmask) {
		//read the row of data and add to the ListView
		if (fread( &st,      sizeof(st),  1, fTmpR)!=1) goto err;
		if (fread( &obj_len, sizeof(int), 1, fTmpR)!=1) goto err;
		log(_T("DBG: tracelog_read: 2: obj_len=%i.\n"),obj_len);
		if (obj_len) {
			obj=new _TCHAR[obj_len + 1];
			log(_T("DBG: tracelog_read: 3: obj_len*sizeof(_TCHAR)=%i.\n"),obj_len*sizeof(_TCHAR));
			if (fread( obj, obj_len*sizeof(_TCHAR), 1, fTmpR)!=1) goto err;
			//obj[obj_len] = _T('\0');
		}
		if (fread( &msg_len, sizeof(int), 1, fTmpR)!=1) goto err;
		log(_T("DBG: tracelog_read: 4: msg_len=%i.\n"),msg_len);
		if (msg_len) {
			log(_T("DBG: tracelog_read: 4: msg_len*sizeof(_TCHAR)=%i.\n"),msg_len*sizeof(_TCHAR));
			msg=new _TCHAR[msg_len + 1];
			if (fread( msg, msg_len*sizeof(_TCHAR), 1, fTmpR)!=1) goto err;
			//msg[msg_len] = _T('\0');
		}
		log(_T("DBG: tracelog_read: 5: obj_len:%i, msg_len:%i\n"), obj_len, msg_len);

		//add item to the list view
		addListViewItem(hwndLV, bmask, &st, obj, msg);
		
		if (obj) delete []obj;
		if (msg) delete []msg;
		log(_T("DBG: tracelog_read: exit 1.\n"));
		return 1;
	} else {
		//skip the row
		fseek( fTmpR, pos+len, SEEK_SET );
		if ( ftell(fTmpR) != pos+len ) goto err;
		log(_T("DBG: tracelog_read: exit 0.\n"));
		return 0;
	}
	err:
	//return pointer to the previous position
	fseek( fTmpR, pos, SEEK_SET );
	if (obj) delete []obj;
	if (msg) delete []msg;
	log(_T("DBG: tracelog_read: exit -1.\n"));
	return -1;
}


//resets the read pointer for the buffer to the begining of it
BOOL tracelog_rewind(){
	if(!fseek( fTmpR, 0, SEEK_SET ))return true;
	return false;
}



extern "C" BOOL __stdcall tracelog_title(WCHAR *s){
	if(showTraceLog){
		SetWindowText(hwndMain,s);
		notifyTrayIcon(hwndMain, NIM_MODIFY, NULL, s );
	}
	return true;
}
WCHAR *_tounicode(char*c){
	if(!c)return NULL;
	int i=strlen(c);
	WCHAR *wc=new WCHAR[i+1];
	wsprintf(wc,L"%hs",c);
	return wc;
}
//ansii functions
extern "C" BOOL __stdcall tracelog_addA( int mask, char * obj, char * msg){
	WCHAR *wobj=_tounicode(obj);
	WCHAR *wmsg=_tounicode(msg);
	BOOL b=tracelog_add( mask, wobj, wmsg);
	if(wobj)delete []wobj;
	if(wmsg)delete []wmsg;
	return b;
}
extern "C" BOOL __stdcall tracelog_titleA(char *s){
	WCHAR *ws=_tounicode(s);
	BOOL b=tracelog_title(ws);
	if(ws)delete []ws;
	return b;
}

extern "C" BOOL __stdcall install_message_callbackA(char *pbDBMS,void* con){
	WCHAR *ws=_tounicode(pbDBMS);
	BOOL b=install_message_callback(ws,con);
	if(ws)delete []ws;
	return b;
}

extern "C" BOOL __stdcall remove_message_callbackA(char *pbDBMS,void*con){
	WCHAR *ws=_tounicode(pbDBMS);
	BOOL b=remove_message_callback(ws,con);
	if(ws)delete []ws;
	return b;
}



